/***************************************************************************
 * This code and information is provided "as is" without warranty of any   *
 * kind, either expressed or implied, including but not limited to the     *
 * implied warranties of merchantability and/or fitness for a particular   *
 * purpose.                                                                *
 *                                                                         *
 * Copyright (C) 2008 Teridian SemiConductor, Corp. All Rights Reserved.   *
 *                                                                         *
 ***************************************************************************/
//**************************************************************************//
//                                                                          // 
//  DESCRIPTION: TSCP-CCID                                                  //
//               PacketParser.cs                                            //
//  PacketParser is an object that interprets the message received from     //
//  serial port. The input for this object is a message string              //
//                                                                          //
//  AUTHOR:  SD                                                             //
//                                                                          //
//  HISTORY: 2007 SEPTEMBER 26; First Version Released 1.0                  //
//           2007 DECEMBER  14; Version 2.00 Release                        //
//                                                                          //
//**************************************************************************//
/***************************************************************************
 * This code was adapted from PacketParser.cs which was originally written *
 * by Teridian SemiConductor, Corp. It has been modified from its original *
 * version to allow for use of the Teridian board in the .NET Micro        *
 * Framework.                                                              *
 * LAST MODIFIED: 2010 April 23                                            *
 * Copyright (C) 2009 Gemalto                                              *
 ***************************************************************************/
/***************************************************************************
 *  PacketParser.cs is part of the PC/SC Micro API for the .NET Micro      *
 *  Framework.                                                             *
 *                                                                         *
 *  The PC/SC Micro API for the .NET Micro Framework is free software:     *
 *  you can redistribute it and/or modify it under the terms of the GNU    *
 *  General Public License as published by the Free Software Foundation,   *
 *  either version 2 of the License, or (at your option) any later version.*
 *                                                                         *
 *  The PC/SC Micro API for the .NET Micro Framework is distributed in     *
 *  the hope that it will be useful, but WITHOUT ANY WARRANTY; without even*
 *  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR    *
 *  PURPOSE.  See the GNU General Public License for more details.         *
 *                                                                         *
 *  You should have received a copy of the GNU General Public License      *
 *  along with the PC/SC Micro API for the .NET Micro Framework. If not,   *
 *  see <http://www.gnu.org/licenses/>.                                    *
 ***************************************************************************/
using System;
using System.Text;

namespace PCSCMicro
{
    class PacketParser
    {
        // Definition of acknowledge bytes
        const string ACK = "C0AAC0";
        const string NACK = "C055C0";

        // Definition of statuses
        const string BAD_ATR_TCK = "F7";
        const string BAD_ATR_TS = "F8";
        const string KEYPAD_TIMEOUT = "F9";
        const string BAD_PASSCODE = "FA";
        const string HW_ERROR = "FB";
        const string ICC_OTHER_ERROR = "FC";
        const string SECMODE_NOT_SET = "FD";
        const string ICC_MUTE = "FE";
        const string CMD_ABORTED = "FF";

        // Definitions of response locations
        const int statusWordLen = 2;
        const int statusWordStrLen = statusWordLen * 2;

        // Potential error types
        internal enum ErrorType
        {
            None, NoResponse, Timeout, NACK, IllegalMessage, CRCCheck,
            CommandAborted, ICCMute, HWError, BadATRTS, BadATRTCK,
            KeypadTimeout, BadPasscode, IccOtherError, SecModeNotSet,
            Unknown
        }

        // Definition of accessible variables
        string Message;
        internal string AckMsg;
        internal string ResMsg;
        internal string ErrorMessage;
        internal string statusWord;
        internal string resp;
        internal int respLen;
        internal ErrorType ErrorCode;

        // Only one constructor to discourage bad uFW coding practices
        // (inherited code had another constructor with msg as parameter)
        internal PacketParser()
        {
            Message = "";
        }

        // Encouraged to call this every time new message is received
        // instead of creating new PacketParser each time
        internal void GetPacket(string msg)
        {
            Message = msg;
            ParseMe();
        }

        // Parse message data
        // This function assumes the desired message is in Message
        // At termination, resp holds the response which is of length
        // respLen, ErrorCode contains the ErrorType associated with
        // Message and ErrorMessage contains the corresponding string,
        // AckMsg contains the acknowledgement string (either ACK or NACK)
        // and ResMsg contains the rest of Message that is not a 
        // part of AckMsg
        internal void ParseMe()
        {
            // No response
            if ((Message == "") || (Message == null))
            {
                AckMsg = "";
                ResMsg = "";
                ErrorMessage = "No response";
                ErrorCode = ErrorType.NoResponse;
            }
            // ACK received, response falls into infinite loop
            else if (Message == ACK)
            {
                AckMsg = ACK;
                ResMsg = "";
                ErrorMessage = "Timeout expired & no data received." + "\r\n" + "Increasing timeout may help. ";
                ErrorCode = ErrorType.Timeout;
            }
            //NACK received
            else if (Message == NACK)
            {
                AckMsg = NACK;
                ResMsg = "";
                ErrorMessage = "NACK limt reached";
                ErrorCode = ErrorType.NACK;
            }
            // Illegal message
            // Minimum length of a legal message is 15
            // 
            // C0AAC0 C0XXYYZZ CRC1 CRC2 C0
            //   (6)     (8)   (1)  (1)  (1) = 15 bytes total
            else if (Message.Length < 15)
            {
                AckMsg = "";
                ResMsg = Message;
                ErrorMessage = "Illegal message";
                ErrorCode = ErrorType.IllegalMessage;
            }
            // Parse data to see if there's any error
            else
            {
                AckMsg = Message.Substring(0, ACK.Length);                              // ACK string
                ResMsg = Message.Substring(ACK.Length, Message.Length - ACK.Length);
                // Example: C0AAC0C00041FB...,  Message.Substring(6, 2) = 'FB'
                // Handle Error
                // Get the error code, for example '41FB'
                if ((Message.Substring(0, ACK.Length) == ACK) && (Message.Substring(8, 2) == "00"))
                {
                    ErrorMessage = ResMsg.Substring(4, 4);
                    // Parse ResMsg to understand the error type
                    // Message.Substring(6, 2)
                    // Example: ResMsg.Substring(6, 2) = 'FB'
                    //          ResMsg.Substring(4, 4) = '41FB'
                    switch (ResMsg.Substring(6, 2))
                    {
                        case CMD_ABORTED:
                            ErrorCode = ErrorType.CommandAborted;
                            break;
                        case ICC_MUTE:
                            ErrorCode = ErrorType.ICCMute;
                            break;
                        case HW_ERROR:
                            ErrorCode = ErrorType.HWError;
                            break;
                        case BAD_ATR_TS:
                            ErrorCode = ErrorType.BadATRTS;
                            break;
                        case BAD_ATR_TCK:
                            ErrorCode = ErrorType.BadATRTCK;
                            break;
                        case KEYPAD_TIMEOUT:
                            ErrorCode = ErrorType.KeypadTimeout;
                            break;
                        case BAD_PASSCODE:
                            ErrorCode = ErrorType.BadPasscode;
                            break;
                        case ICC_OTHER_ERROR:
                            ErrorCode = ErrorType.IccOtherError;
                            break;
                        case SECMODE_NOT_SET:
                            ErrorCode = ErrorType.SecModeNotSet;
                            break;
                        default:
                            ErrorCode = ErrorType.Unknown;
                            break;
                    }
                }
                
                else
                {
                    // If there is response, get it
                    if (ResMsg.Length > AckMsg.Length + 2 + 6)
                    {
                        resp = ResMsg.Substring(ACK.Length + 2, ResMsg.Length - 6 - (ACK.Length + 2));
                    }
                    // Else get the status
                    else
                    {
                        resp = ResMsg.Substring(ACK.Length + 2, statusWordStrLen);
                    }
                    respLen = resp.Length;
                    statusWord = ResMsg.Substring(ACK.Length + 2 + respLen-4, statusWordStrLen);
                    ErrorMessage = "";
                    ErrorCode = ErrorType.None;
                }
            }
        }
    }
}